#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Xml.Serialization;
using Adoor.Expression;
using Adoor.Expression.AST;

namespace Adoor.Relational
{
    // RelationalQuery vs. RelationalOperator:
    // - RelationalQuery is a complete query
    // - RelationalQuery has columns of type DataColumns (of the form "Name")
    // - RelationalOperator is a partial query used in the from clause of a SQL statement
    // - RelationalOperator has columns of type ExpressionName (of the form "Correlation.Name")
    // - RelationalOperator has a list of correlations used by the join operator
    public abstract class RelationalQuery: RelationalElement
    {

        [XmlIgnore]
        public abstract ICollection<DataColumn> DataColumns { get; }

        private bool distinct;
        private Int64 topCount = Int64.MaxValue;

        /// <summary>
        /// Returns a new relational operator based on the current query (i.e. its columns are qualified by alias).
        /// </summary>
        /// <param name="alias">The alias used to qualify the columns</param>
        public RelationalOperator Rename(string alias)
        {
            return new AliasOperator(this, alias);
        }

        public bool Distinct
        {
            get { return this.distinct; }
            set { this.distinct = value; }
        }

        public Int64 TopCount
        {
            get { return this.topCount; }
            set { this.topCount = value; }
        }

        public abstract void EvaluateFrom(IRelationalQueryEvaluator e);

        private object parent;
        public object Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        private object tag;
        public object Tag
        {
            get { return tag; }
            set { tag = value; }
        }
    }

    /// <summary>
    /// A relational query evaluator is an object that knows how to evaluate relational queries. To evaluate
    /// a relational query, call IRelationalQuery.EvaluateFrom(IRelationalQueryEvaluator). EvaluateFrom
    /// decomposes the relational query into basic relational queries which specialized descendants of the
    /// evaluator know how to evaluate (for the aficionados of design patterns the 
    /// IRelationalQuery/IRelationalQueryEvaluator couple follows the GOF's "visitor pattern").
    /// The result of the evaluation as well as potential extra parameters are generally exposed by the
    /// specialized evaluator.
    /// </summary>
    public interface IRelationalQueryEvaluator
    {
        /// <summary>
        /// Evaluates the basic relational query Scan.
        /// The precise meaning of "evaluate" is defined by the class supporting the interface.
        /// </summary>
        /// <param name="s">The basic relational query Scan to evaluate.</param>
        void EvaluateScan(ScanQuery s);

        /// <summary>
        /// Evaluates the basic relational query Union.
        /// The precise meaning of "evaluate" is defined by the class supporting the interface.
        /// </summary>
        /// <param name="u">The basic relational query Union to evaluate.</param>
        void EvaluateUnion(UnionQuery u);

        /// <summary>
        /// Evaluates the basic relational query Unqualifier.
        /// The precise meaning of "evaluate" is defined by the class supporting the interface.
        /// </summary>
        /// <param name="u">The basic relational query Unqualifier to evaluate.</param>
        void EvaluateUnqualifier(UnqualifierQuery u);

        /// <summary>
        /// Evaluates the basic relational query Project.
        /// The precise meaning of "evaluate" is defined by the class supporting the interface.
        /// </summary>
        /// <param name="p">The basic relational query Project to evaluate.</param>
        void EvaluateProject(ProjectQuery p);

        void EvaluateInsert(InsertQuery p);
        void EvaluateUpdate(UpdateQuery p);
        void EvaluateDelete(DeleteQuery p);
    }

    /// <summary>
    /// Basic relational query. Describes the set of rows contained in the table named "SourceName"
    /// </summary>
    public class ScanQuery : RelationalQuery//, IScanQuery
    {
        public ScanQuery() // required by XML serialization
        {
        }

        public ScanQuery(string sourceName, ICollection<DataColumn> dataColumns)
        {
            if (dataColumns == null)
                throw new ArgumentNullException("dataColumns");
            this.sourceName = sourceName;
            this.dataColumnsInfo = new DataColumn[dataColumns.Count];
            int i = 0;
            foreach (DataColumn o in dataColumns)
            {
                this.dataColumnsInfo[i++] = (DataColumn)o;
            }
        }

        // used for serialization only
        [XmlArray("DataColumns")]
        [XmlArrayItem("DataColumn")]
        public override ICollection<DataColumn> DataColumns
        {
            get
            {
                return this.dataColumnsInfo;
            }
        }

        public string SourceName
        {
            get { return this.sourceName; }
            set { this.sourceName = value; }
        }


        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateScan(this);
        }

        private DataColumn[] dataColumnsInfo;
        private string sourceName;
    }


    public class ProjectQuery : RelationalQuery//, IProjectQuery
    {
        // for serialization purposes
        public ProjectQuery()
        {
        }

        public ProjectQuery(RelationalOperator source, ICollection<SelectExpression> columns2)
        {
            this.source = source;
            if (this.Source != null)
                source.Parent = this;
            this.columns2 = new List<SelectExpression>(columns2);
        }

        [XmlIgnore]
        public ICollection<SelectExpression> Columns
        {
            get { return this.columns2; }
        }

        [XmlIgnore]
        public override ICollection<DataColumn> DataColumns
        {
            get
            {
                DataColumn[] result = new DataColumn[this.columns2.Count];
                int i = 0;
                foreach (SelectExpression c in this.columns2)
                {
                    if (c.Alias == string.Empty)
                    {
                        if (c.Expr is ExpressionName)
                        {
                            DataColumn dci = new DataColumn();
                            dci.ColumnName = ((ExpressionName)c.Expr).Value;
                            result[i++] = dci;
                        }
                        else
                        {
                            throw new Exception(c.Expr.GetType().Name + " should be aliased");
                        }
                    }
                    else
                    {
                        DataColumn dci = new DataColumn();
                        dci.ColumnName = c.Alias;
                        result[i++] = dci;
                    }
                }
                return result;
            }
        }

        [XmlArray("Columns")]
        [XmlArrayItem(typeof(SelectExpression))]
        public SelectExpression[] ColumnInfos
        {
            get
            {
                return this.columns2.ToArray();
            }

            set
            {
                this.columns2 = new List<SelectExpression>(value);
            }
        }

        [XmlIgnore]
        public RelationalOperator Source
        {
            get { return this.source; }
            set
            {
                this.source = value;
                if (this.Source != null)
                    this.source.Parent = this;
            }
        }

        [XmlElement(typeof(AliasOperator))]
        //[XmlElement(typeof(CrossOperator))]
        [XmlElement(typeof(FilterOperator))]
        //[XmlElement(typeof(IntersectOperator))]
        [XmlElement(typeof(JoinOperator))]
        //[XmlElement(typeof(SortOperator))]
        public object SourceInfo
        {
            get { return this.source; }
            set { this.source = (RelationalOperator)value; }
        }

        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateProject(this);
        }

        private RelationalOperator source;
        private List<SelectExpression> columns2;
    }

    /// <summary>
    /// Basic relational query. Describes the set of rows contained in the mergin of the sets described by "Sources".
    /// </summary>
    public class UnionQuery : RelationalQuery//, IUnionQuery
    {
        public UnionQuery()
        {
        }

        public UnionQuery(RelationalQuery source1, RelationalQuery source2, ICollection<DataColumn> dataColumns)
        {
            this.dataColumnsInfo = new DataColumn[dataColumns.Count];
            int i = 0;
            foreach (DataColumn o in dataColumns)
            {
                this.dataColumnsInfo[i++] = (DataColumn)o;
            }
            Add(source1);
            Add(source2);
        }

        [XmlIgnore]
        public override ICollection<DataColumn> DataColumns
        {
            get { return this.dataColumnsInfo; }
        }

        public ICollection<RelationalQuery> Sources
        {
            get { return this.sources; }
        }

        [XmlArray("Sources")]
        [XmlArrayItem(typeof(ScanQuery))]
        [XmlArrayItem(typeof(UnionQuery))]
        public RelationalQuery[] SourceInfos
        {
            get
            {
                return this.sources.ToArray();
            }

            set
            {
                this.sources = new List<RelationalQuery>(value);
            }
        }

        public void Add(RelationalQuery source)
        {
            source.Parent = this;
            if (source is UnionQuery)
            {
                foreach (RelationalQuery r in ((UnionQuery)source).Sources)
                    sources.Add(r);
            }
            else
                sources.Add(source);
        }

        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateUnion(this);
        }

        private DataColumn[] dataColumnsInfo;
        private List<RelationalQuery> sources = new List<RelationalQuery>();
    }

    /// <summary>
    /// Basic relational query. Transforms the relational operator "Source" into a relational query by unqualifiying
    /// column names.
    /// </summary>
    public class UnqualifierQuery : RelationalQuery//, IUnqualifierQuery
    {
        public UnqualifierQuery()
        {
        }

        public UnqualifierQuery(RelationalOperator source)
        {
            this.source = source;
            if (source != null)
                source.Parent = this;
            this.dataColumns = new DataColumn[this.source.Columns.Count];
            int i = 0;
            foreach (ExpressionName rc in this.source.Columns)
                this.dataColumns[i++] = new DataColumn(rc.Value);
            // TODO: check for duplicate names
        }

        [XmlIgnore]
        public override ICollection<DataColumn> DataColumns
        {
            get { return this.dataColumns; }
        }

        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateUnqualifier(this);
        }

        [XmlIgnore]
        public RelationalOperator Source
        {
            get { return this.source; }
            set
            {
                this.source = value;
                if (this.source != null)
                    this.source.Parent = this;
            }
        }

        [XmlElement(typeof(AliasOperator))]
        [XmlElement(typeof(FilterOperator))]
        [XmlElement(typeof(JoinOperator))]
        public object SourceInfo
        {
            get { return this.source; }
            set { this.source = (RelationalOperator)value; }
        }

        private RelationalOperator source;
        private DataColumn[] dataColumns;
    }

    public class InsertQuery : RelationalQuery//, IScanQuery
    {
        public InsertQuery()
        {
        }

        public InsertQuery(string sourceName, ICollection<DataColumn> dataColumns, ExpressionNode[] values)
            : this(sourceName, dataColumns)
        {
            this.values = values;
        }

        public InsertQuery(string sourceName, ICollection<DataColumn> dataColumns, RelationalQuery innerQuery)
            : this(sourceName, dataColumns)
        {
            this.innerQuery = innerQuery;
        }

        private InsertQuery(string sourceName, ICollection<DataColumn> dataColumns)
        {
            if (dataColumns == null)
                throw new ArgumentNullException("dataColumns");
            this.sourceName = sourceName;
            this.dataColumnsInfo = new DataColumn[dataColumns.Count];
            int i = 0;
            foreach (DataColumn o in dataColumns)
            {
                 this.dataColumnsInfo[i++] = (DataColumn)o;
            }
        }


        // used for serialization only
        [XmlArray("DataColumns")]
        [XmlArrayItem("DataColumn")]
        public override ICollection<DataColumn> DataColumns
        {
            get
            {
                return this.dataColumnsInfo;
            }
        }

        public string SourceName
        {
            get { return this.sourceName; }
            set { this.sourceName = value; }
        }

        public ExpressionNode[] Values
        {
            get { return values; }
        }

        public RelationalQuery InnerQuery
        {
            get { return innerQuery; }
        }


        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateInsert(this);
        }

        private DataColumn[] dataColumnsInfo;
        private string sourceName;
        private ExpressionNode[] values;
        private RelationalQuery innerQuery;

    }

    public class UpdateQuery : RelationalQuery//, IScanQuery
    {
        public UpdateQuery(IList<RelationalQuery> innerQueries)
        {
            this.innerQueries = new List<RelationalQuery>(innerQueries);
        }

        public UpdateQuery(string sourceName, SetExpression[] setExpressions, UpdateQueryTypes queryType, RelationalOperator relationOperator)
        {
            this.updateQueryType = queryType;
            this.setExpressions = new List<SetExpression>(setExpressions);
            this.sourceName = sourceName;
            this.dataColumnsInfo = new DataColumn[0];
            this.innerQueries = new List<RelationalQuery>();
            this.relationOperator = relationOperator;
        }


        // used for serialization only
        [XmlArray("DataColumns")]
        [XmlArrayItem("DataColumn")]
        public override ICollection<DataColumn> DataColumns
        {
            get
            {
                return this.dataColumnsInfo;
            }
        }

        public string SourceName
        {
            get { return this.sourceName; }
            set { this.sourceName = value; }
        }

        private List<RelationalQuery> innerQueries;
        private List<SetExpression> setExpressions;

        public IList<SetExpression> SetExpressions
        {
            get { return setExpressions; }
        }

        public IList<RelationalQuery> InnerQueries
        {
            get { return innerQueries; }
        }

        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateUpdate(this);
        }

        private DataColumn[] dataColumnsInfo;
        private string sourceName;
        private UpdateQueryTypes updateQueryType;
        private RelationalOperator relationOperator;

        public RelationalOperator RelationOperator
        {
            get { return relationOperator; }
        }

        public UpdateQueryTypes UpdateQueryType
        {
            get { return updateQueryType; }
        }

        public enum UpdateQueryTypes
        {
            Update,
            Delete,
            Insert
        }
    }

    public class DeleteQuery : RelationalQuery//, IScanQuery
    {
        public DeleteQuery(IList<RelationalQuery> innerQueries)
        {
            this.innerQueries = new List<RelationalQuery>(innerQueries);
        }

        public DeleteQuery(string sourceName, RelationalOperator relationOperator, ExpressionName enoid)
        {
            this.sourceName = sourceName;
            this.dataColumnsInfo = new DataColumn[0];
            this.innerQueries = new List<RelationalQuery>();
            this.relationOperator = relationOperator;
            this.enoid = enoid;
        }


        // used for serialization only
        [XmlArray("DataColumns")]
        [XmlArrayItem("DataColumn")]
        public override ICollection<DataColumn> DataColumns
        {
            get
            {
                return this.dataColumnsInfo;
            }
        }

        public string SourceName
        {
            get { return this.sourceName; }
            set { this.sourceName = value; }
        }

        private List<RelationalQuery> innerQueries;

        public IList<RelationalQuery> InnerQueries
        {
            get { return innerQueries; }
        }

        public override void EvaluateFrom(IRelationalQueryEvaluator e)
        {
            e.EvaluateDelete(this);
        }

        private DataColumn[] dataColumnsInfo;
        private string sourceName;
        private RelationalOperator relationOperator;
        private ExpressionName enoid;

        public ExpressionName ExpressionNameOID
        {
            get { return enoid; }
        }

        public RelationalOperator RelationOperator
        {
            get { return relationOperator; }
        }
    }
}